/*
 * Copyright (C) 2011 Kamran Zafar
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 * */
 
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.Reflection;
using System.Windows.Forms;
using System.Reflection.Emit;

namespace org.kamranzafar.hotfusion
{
	class Binder : IDisposable
	{
		// Compressed files ready to embed as resource
		protected List<BindEntry> fileSettings=new List<BindEntry>();
		protected Settings extractorSettings;

		public Binder(Settings extractorSettings) {
			this.extractorSettings = extractorSettings;
		}

		public void AddFile(BindEntry fs)
		{
			if(fs.Type==1){
				String newFileName=MD5.digest(fs.FilesEntry.FileName);
				
				if(fs.FilesEntry.Compress){
					string tmpgz = Path.Combine(Path.GetTempPath(), newFileName+Path.GetExtension(fs.FilesEntry.FileName)) + ".gz";

					using (Stream file = File.OpenRead(fs.FilesEntry.FileName))
					{
						using (Stream gzFile = File.Create(tmpgz))
						{
							using (Stream gzip = new GZipStream(gzFile, CompressionMode.Compress))
							{
								byte[] buffer = new byte[4096];
								int numRead;
								while ((numRead = file.Read(buffer, 0, buffer.Length)) != 0)
								{
									gzip.Write(buffer, 0, buffer.Length);
								}

								gzip.Flush();
								gzip.Close();
							}
						}
						
						file.Close();
					}
					
					fs.FilesEntry.TmpGzPath=tmpgz;
				}
				
				fs.FilesEntry.HashedName=newFileName;
				fileSettings.Add(fs);
			} else if(fs.Type == 3){
				BindEntry.CommandEntry cmd = fs.CmdEntry;
				
				if(cmd!=null){
					cmd.CommandFileName=Path.GetTempFileName();
					fileSettings.Add(fs);
				}
			}
		}

		public void CompileArchive(string archiveFilename)
		{
			CompileArchive(archiveFilename, extractorSettings.IconFile);
		}

		public void CompileArchive(string archiveFilename, string iconFilename)
		{
			CodeDomProvider csc = new CSharpCodeProvider();
			CompilerParameters cp = new CompilerParameters();

			cp.GenerateExecutable = true;
			cp.OutputAssembly = archiveFilename;
			cp.CompilerOptions = "/target:winexe";

			// Custom option to run a file after extraction
			if (!string.IsNullOrEmpty(iconFilename))
			{
				cp.CompilerOptions += " /win32icon:" + iconFilename;
			}
			cp.ReferencedAssemblies.Add("System.dll");
			cp.ReferencedAssemblies.Add("System.Windows.Forms.dll");
			cp.ReferencedAssemblies.Add("mscorlib.dll");

			// Add compressed files as resource
			foreach(BindEntry fs in fileSettings){
				if(fs.Type==1){
					if(fs.FilesEntry.Compress){
						fs.Id = cp.EmbeddedResources.Add(fs.FilesEntry.TmpGzPath);
					}
					else{
						fs.Id = cp.EmbeddedResources.Add(fs.FilesEntry.FileName);
					}
				}else if(fs.Type==3){
					fs.Id=cp.EmbeddedResources.Add(fs.CmdEntry.CommandFileName);
				}
			}
			
			// Generate source
			String source=new StubGenerator(extractorSettings).generate(fileSettings);
			
			// Compile standalone executable with input files embedded as resource
			CompilerResults cr = csc.CompileAssemblyFromSource(cp,source);
			
			// yell if compilation error
			if (cr.Errors.Count > 0)
			{
				string msg = "Errors building " + cr.PathToAssembly;

				foreach (CompilerError ce in cr.Errors)
				{
					msg += Environment.NewLine + ce.ToString();
				}
				throw new ApplicationException(msg);
			}
		}
		
		#region IDisposable Members

		public void Dispose()
		{
			foreach(BindEntry fs in fileSettings){
				if(fs.Type==1){
					if(fs.FilesEntry.Compress)
						File.Delete(fs.FilesEntry.TmpGzPath);
				}else if(fs.Type==3){
					File.Delete(fs.CmdEntry.CommandFileName);
				}
			}

			GC.SuppressFinalize(this);
		}

		#endregion
	}
}
